package com.atguigu.yygh.hosp.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.yygh.hosp.repository.DepartmentRepository;
import com.atguigu.yygh.hosp.repository.HospitalRepository;
import com.atguigu.yygh.hosp.repository.ScheduleRepository;
import com.atguigu.yygh.hosp.service.ScheduleService;
import com.atguigu.yygh.model.hosp.BookingRule;
import com.atguigu.yygh.model.hosp.Department;
import com.atguigu.yygh.model.hosp.Hospital;
import com.atguigu.yygh.model.hosp.Schedule;
import com.atguigu.yygh.vo.hosp.BookingScheduleRuleVo;
import com.atguigu.yygh.vo.hosp.ScheduleOrderVo;
import org.apache.ibatis.jdbc.Null;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class ScheduleServiceImpl implements ScheduleService {
    @Autowired
    ScheduleRepository scheduleRepository;

    @Autowired
    MongoTemplate mongoTemplate;
    @Autowired
    HospitalRepository hospitalRepository;
    @Autowired
    DepartmentRepository departmentRepository;



    @Override
    public void save(Map<String, Object> map) {
        //签名校验
        String jsonString = JSON.toJSONString(map);
        Schedule schedule = JSON.parseObject(jsonString, Schedule.class);
        Schedule scheduleFromMongo = scheduleRepository.findByHoscodeAndHosScheduleId(schedule.getHoscode(), schedule.getHosScheduleId());
        if(scheduleFromMongo==null){
            schedule.setCreateTime(new Date());
            schedule.setUpdateTime(new Date());
            scheduleRepository.insert(schedule);
        }else{
            schedule.setId(scheduleFromMongo.getId());
            schedule.setCreateTime(scheduleFromMongo.getCreateTime());
            schedule.setUpdateTime(new Date());
            scheduleRepository.save(schedule);
        }
    }

    //查询排班
    @Override
    public Page<Schedule> selectPage(Map<String, Object> map) {
        String hoscode = String.valueOf(map.get("hoscode"));
        String depcode = String.valueOf(map.get("depcode"));
        int page = Integer.parseInt(String.valueOf(map.get("page")));
        int limit = Integer.parseInt(String.valueOf(map.get("limit")));

        Schedule schedule=new Schedule();
        schedule.setHoscode(hoscode);
        schedule.setDepcode(depcode);
//如果需要模糊查询 hoscode like ?
/*        ExampleMatcher exampleMatcher=ExampleMatcher.matching()
                .withIgnoreCase(true).withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);

        Example<Schedule> example=Example.of(schedule,exampleMatcher);*/

        Example<Schedule> example=Example.of(schedule);

        //分页, 创建时间降序排序
        Sort sort=Sort.by(Sort.Direction.DESC,"createTime");
        Pageable pageable= PageRequest.of(page-1,limit,sort);

        Page<Schedule> all = scheduleRepository.findAll(example, pageable);

        return all;
    }

    //删除排班
    @Override
    public void remove(Map<String, Object> map) {
        String hoscode = String.valueOf(map.get("hoscode"));
        String hosScheduleId = String.valueOf(map.get("hosScheduleId"));

        Schedule scheduleFromMongo = scheduleRepository.findByHoscodeAndHosScheduleId(hoscode, hosScheduleId);
        if(scheduleFromMongo!=null){
            String id = scheduleFromMongo.getId();
            scheduleRepository.deleteById(id);
        }

      //  scheduleRepository.removeByHoscodeAndHosScheduleId(hoscode,hosScheduleId);

    }

    @Override
    public Map getScheduleBookingRule(Long pageNum, Long pageSize, String hoscode, String depcode) {

        //针对指定医院指定科室下的排班,按照workDate进行分组聚合,每一组提取一些属性,封装成bookingScheduleRuleVo对象,并且聚合的结果按照workDate排序和分页
        Aggregation agg=Aggregation.newAggregation(
                Aggregation.match(Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode)),
                Aggregation.group("workDate")
                        .first("workDate").as("workDate")
                        .first("workDate").as("workDateMd")
                        .count().as("docCount")
                        .sum("reservedNumber").as("reservedNumber")
                        .sum("availableNumber").as("availableNumber"),
                Aggregation.sort(Sort.Direction.ASC,"workDate"),
                Aggregation.skip((pageNum-1)*pageSize),
                Aggregation.limit(pageSize)
        );
        AggregationResults<BookingScheduleRuleVo> aggregate = mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);
        List<BookingScheduleRuleVo> list = aggregate.getMappedResults();
        
        //星期
        for (BookingScheduleRuleVo ruleVo : list) {
            Date workDate = ruleVo.getWorkDate();  //日期
            ruleVo.setDayOfWeek(this.getDayOfWeek(workDate)); //星期
        }
        
        Map map=new HashMap();
        map.put("bookingScheduleRuleVoList",list);
        map.put("total",this.getTotal(hoscode, depcode));
        return map;
    }

    //根据医院编号+科室编号+排班日期  查询排班列表
    @Override
    public List<Schedule> getScheduleDetail(String hoscode, String depcode, String workDate) {
        //将字符串年月日转换成Date对象
        DateTime dateTime = new DateTime(workDate);
        Date date = dateTime.toDate();
        List<Schedule> list = scheduleRepository.findByHoscodeAndDepcodeAndWorkDate(hoscode, depcode, date);

        return list;
    }

    /**
     *  (1)针对15-25号内所有的排班进行聚合--->分页
     *  (2)提取第一页的日期 15-19号,针对该日期范围内的排班进行聚合
     *      Date date = new DateTime().plusDays(1).toDate(); new Datetime是今天,plusDays就是加多少天
     *     ①先将日期计算出来
     *         List<Date> dateList = null;
     *         Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode).and("workDate").in(dateList);
     *
     *查询的三块数据:
     *     (1)日期的vo列表
     *     (2)总日期个数
     *     (3)医院名称+科室名称+年月
     */
    @Override
    public Map getBookingSchedule(String hoscode, String depcode, Integer pageNum, Integer pageSize) {
       /*
            1. 找到日期范围,从今天开始,到cycle(+1)个日期
                根据医院编号-->医院-->预约规则(bookingRule)-->cycle
                日期范围显示10天  如果当前时间超过放号时间,cycle+1
                放号时间是08:30
                判断出cycle以后,就根据cycle,创建日期集合
        */
        Hospital hospital = hospitalRepository.findByHoscode(hoscode);
        BookingRule bookingRule = hospital.getBookingRule();
        Integer cycle = bookingRule.getCycle();

        //1.判断当前时间是不是在放号时间之前08:30(先找到一共可以预约挂号多少天)
        DateTime dateTime = this.getDateTime(new Date(), bookingRule.getReleaseTime());
        if(dateTime.isBeforeNow()){  //isBeforeNow判断时间是不是在这之前
            cycle ++;
        }
        //2.根据cycle.创建日期集合,例如:cycle=11,创建11个日期对象(只表示年月日,不需要时分秒)(将这些天的日期找出来)
        //所有日期集合
        List<Date> list = this.getDateList(cycle);

        //4.从所有的日期中,截取第一页的日期范围,然后针对该日期范围内的排班进行聚合,得到的所有vo,都是第一页的vo
        int begin=(pageNum-1)*pageSize;    //开始的索引
        int end=begin+pageSize; //5 10 15  如果最后一页的数据不足5个就会出现数组越界   结束的索引
        //防止数组越界,就让最后的等于list的长度,如果list=11,那么end=11
        if(end>list.size()){
            end=list.size();
        }
        List<Date> pageList=new ArrayList<>();
        for (int i = begin; i <end ; i++) {
            Date date = list.get(i);
            pageList.add(date);
        }

        //3.针对dateList(所有的日期)的聚合
        // 按照workDate进行分组

        Aggregation agg=Aggregation.newAggregation(
                Aggregation.match(Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode).and("workDate").in(pageList)),
                Aggregation.group("workDate")
                        .first("workDate").as("workDate")
                        .first("workDate").as("workDateMd")
                        .count().as("docCount")
                        .sum("reservedNumber").as("reservedNumber")
                        .sum("availableNumber").as("availableNumber"),
                Aggregation.sort(Sort.Direction.ASC,"workDate")

                /*
                不能在聚合里面进行分页,这样只会将所有的数据都分在一页里面
                Aggregation.skip(0L),
                Aggregation.limit(5),
                 */
        );
        AggregationResults<BookingScheduleRuleVo> aggregate = mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);
        List<BookingScheduleRuleVo> voList = aggregate.getMappedResults();

        Map<Date, BookingScheduleRuleVo> map = voList.stream().collect(Collectors.toMap(BookingScheduleRuleVo::getWorkDate, vo -> vo));
        
        //当前页的日期范围内,有些日期没有排班,聚合后就没有vo,需要为这些没有vo的日期,创建一个默认的vo
        //遍历当前页的每一个日期,查询哪个日期没有vo

        //将每个日期对应的vo重新添加到一个新的List集合中
        List<BookingScheduleRuleVo> ruleVoList = new ArrayList<>(); //将vo重新放入到list集合,能够保证日期的顺序

        for (int i = 0; i < pageList.size(); i++) {  //i=0是第一个数据.pageList.size()-1是最后一条数据
            Date date = pageList.get(i); //获取每一个日期
           // BookingScheduleRuleVo vo = this.getVo(date, voList);
            BookingScheduleRuleVo vo = this.getVo2(date, map);
            if(vo==null){
                //该日期没有ruleVo,创建一个默认的ruleVo
                vo=new BookingScheduleRuleVo();
                vo.setWorkDate(date);
                vo.setWorkDateMd(date);
                vo.setDocCount(-1);
                vo.setReservedNumber(-1);
                vo.setAvailableNumber(-1);
            }

            //为vo中的dayOfweek赋值  为星期赋值
            vo.setDayOfWeek(this.getDayOfWeek(vo.getWorkDate()));

            //为每个vo的Status赋值   value = "状态 0：正常    1：即将放号     -1：当天已停止挂号"
            //第一页的第一个vo(今天),如果停止挂号,status设置成-1
            //最后一页的最后一个vo,status设置成1 即将放号
            //其它的vo,
            vo.setStatus(0);

            //第一页的第一个vo(今天),如果停止挂号,status设置成-1
            if(pageNum==1 && i==0){
                String stopTime = bookingRule.getStopTime();
                DateTime dateTime1 = this.getDateTime(new Date(), stopTime);
                if(dateTime1.isBeforeNow()){
                    vo.setStatus(-1);
                }
            }

            //最后一页的最后一个vo,status设置成1 即将放号
            //计算总页数           取整 3             取余 1            加入cycle=11 pageSize=3  所以一共就是4页
           //计算最后一页是第几页 cycle/pageSize+(cycle%pageSize==0?0:1)
            int pages=cycle/pageSize+(cycle%pageSize==0?0:1);
            if(pageNum==pages && i==(pageList.size()-1)){
                vo.setStatus(1);
            }


           // voList.add(vo);不支持add
            ruleVoList.add(vo);
        }
       //日期的vo列表 总日期个数 医院名称+科室名称+年月
        Map result = new HashMap<>();
        result.put("bookingScheduleList",ruleVoList);
        result.put("total",cycle);

        Map baseMap=new HashMap<>();
        baseMap.put("hosname",hospital.getHosname());
        Department department = departmentRepository.findByHoscodeAndDepcode(hoscode, depcode);
        baseMap.put("bigname",department.getBigname());
        baseMap.put("depname",department.getDepname());
        baseMap.put("workDateString",new DateTime().toString("yyyy年MM月")); //2023年08月
        baseMap.put("releaseTime",bookingRule.getReleaseTime());  //放号时间

        result.put("baseMap",baseMap);    //hosname bigname depname workDateString



        return result;
    }

    //根据排班的id查询排班
    @Override
    public Schedule getSchedule(String id) {
        Schedule schedule = scheduleRepository.findById(id).get();
        this.packSchedule(schedule);
        return schedule;
    }

    @Override
    public ScheduleOrderVo getScheduleOrderVo(String scheduleId) {
        ScheduleOrderVo scheduleOrderVo=new ScheduleOrderVo();
        Schedule schedule = scheduleRepository.findById(scheduleId).get();
        Hospital hospital = hospitalRepository.findByHoscode(schedule.getHoscode());
        Department department = departmentRepository.findByHoscodeAndDepcode(schedule.getHoscode(), schedule.getDepcode());

        scheduleOrderVo.setHoscode(schedule.getHoscode());
        scheduleOrderVo.setHosname(hospital.getHosname());
        scheduleOrderVo.setDepcode(schedule.getDepcode());
        scheduleOrderVo.setDepname(department.getDepname());
        scheduleOrderVo.setHosScheduleId(schedule.getHosScheduleId());
        scheduleOrderVo.setTitle(schedule.getTitle());
        scheduleOrderVo.setReserveDate(schedule.getWorkDate()); //排班日期
        scheduleOrderVo.setReserveTime(schedule.getWorkTime()); //排班时间
        scheduleOrderVo.setAvailableNumber(schedule.getAvailableNumber());
        scheduleOrderVo.setAmount(schedule.getAmount());

        //四个time

        //quitTime 退号截止时间
        BookingRule bookingRule = hospital.getBookingRule();
        Integer quitDay = bookingRule.getQuitDay();  //-1
        String quitTime = bookingRule.getQuitTime();  //15:30
        scheduleOrderVo.setQuitTime(this.getQuitTime(quitDay,quitTime,schedule.getWorkDate()));//退号时间


        //startTime 今天的开始挂号时间
        scheduleOrderVo.setStartTime(this.getStartTime(bookingRule));

        //endTime 挂号的结束时间(预约周期内,最后一天的11:30--stopTime)
        scheduleOrderVo.setEndTime(this.getEndTime(bookingRule));

        //stopTime 今天的停止挂号时间
        scheduleOrderVo.setStopTime(this.getStopTime(bookingRule));
        return scheduleOrderVo;
    }

    @Override
    public void updateSchedule(String scheduleId, Integer reservedNumber, Integer availableNumber) {
        Schedule schedule = scheduleRepository.findById(scheduleId).get();
        schedule.setReservedNumber(reservedNumber);
        schedule.setAvailableNumber(availableNumber);
        schedule.setUpdateTime(new Date());
        scheduleRepository.save(schedule);
    }

    //将原来的号源数量+1
    @Override
    public void addAvailableNum(String scheduleId) {
        Schedule schedule = scheduleRepository.findById(scheduleId).get();
        schedule.setAvailableNumber(schedule.getAvailableNumber()+1);
        schedule.setUpdateTime(new Date());
        scheduleRepository.save(schedule);
    }

    private Date getStopTime(BookingRule bookingRule) {
        String s = new DateTime().toString("yyyy-MM-dd") + " " + bookingRule.getStopTime();
        Date date = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(s).toDate();
        return date;
    }

    private Date getEndTime(BookingRule bookingRule) {
        String s = new DateTime().plusDays(bookingRule.getCycle()).toString("yyyy-MM-dd") + " " + bookingRule.getStopTime();
        Date date = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(s).toDate();
        return date;
    }

    private Date getStartTime(BookingRule bookingRule) {
        String releaseTime = bookingRule.getReleaseTime();
        String full = new DateTime().toString("yyyy-MM-dd")+ " "+ releaseTime;
        Date date = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(full).toDate();
        return date;
    }

    private Date getQuitTime(Integer quitDay,String quitTime,Date workDate){
        DateTime dateTime = new DateTime(workDate).plusDays(quitDay); //date转成DateTime,直接传入即可
        String full = dateTime.toString("yyyy-MM-dd")+" "+ quitTime;
        Date date = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(full).toDate();
        return date;
    }

    private void packSchedule(Schedule schedule) {
        //depname hosname dayOfWeek
        String hoscode = schedule.getHoscode();
        Hospital hospital = hospitalRepository.findByHoscode(hoscode);
        String depcode = schedule.getDepcode();
        Department department = departmentRepository.findByHoscodeAndDepcode(hoscode, depcode);

        schedule.getParam().put("depname",department.getDepname());
        schedule.getParam().put("hosname",hospital.getHosname());
        schedule.getParam().put("dayOfWeek",this.getDayOfWeek(schedule.getWorkDate()));
    }

    private BookingScheduleRuleVo getVo2(Date date,Map<Date,BookingScheduleRuleVo> map){
        //将voList转成map  用BookingScheduleVo的workDate(哪天有排班)作为key,BookingScheduleRuleVo对象本身作为value

        BookingScheduleRuleVo ruleVo = map.get(date);
        return ruleVo;
    }

    //看结果集里面是否有相对应的日期
    private BookingScheduleRuleVo getVo(Date date,List<BookingScheduleRuleVo> voList ){
        for (BookingScheduleRuleVo ruleVo : voList) {  //对voList进行遍历.每遍历一次拿到一个ruleVo 有docCout
            Date workDate = ruleVo.getWorkDate();
            String s1 = new DateTime(workDate).toString("yyyy-MM-dd");
            String s2 = new DateTime(date).toString("yyyy-MM-dd");
            if(s1.equalsIgnoreCase(s2)){            //每遍历一次拿到一个workDate和date(每天的日期)进行比较
                return ruleVo;
            }
        }
        return null;
    }



    //判断当前时间是不是在放号时间之前08:30
    private DateTime getDateTime(Date date,String HHmm){
        String s = new DateTime(date).toString("yyyy-MM-dd") + " " + HHmm;
        DateTime dateTime = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(s);
        return dateTime;
    }
    //2.根据cycle.创建日期集合,例如:cycle=11,创建11个日期对象(只表示年月日,不需要时分秒)
    private List<Date> getDateList(Integer cycle){
        List<Date> list = new ArrayList<>();
        for (int i = 0; i < cycle; i++) {
            DateTime dateTime1 = new DateTime().plusDays(i);
            String s = dateTime1.toString("yyyy-MM-dd");
            Date date = DateTimeFormat.forPattern("yyyy-MM-dd").parseDateTime(s).toDate();
            //将查询出的所有日期放在一个集合里面
            list.add(date);
        }
        return list;
    }

    private String getDayOfWeek(Date workDate) {
        DateTime dateTime=new DateTime(workDate);
        int dayOfWeek = dateTime.getDayOfWeek(); //1-周一 ...
        List<String> strings = Arrays.asList("周一", "周二", "周三", "周四", "周五", "周六", "周日");
        return strings.get(dayOfWeek-1);  //集合里面的第一个值是从0开始的
    }

    //针对指定的排班,按照workDate分组聚合,每一组没有提取任何的属性值
    private Integer getTotal(String hoscode,String depcode){
        Aggregation agg=Aggregation.newAggregation(
                Aggregation.match(Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode)),
                Aggregation.group("workDate")
//                        .first("workDate").as("workDate")
//                        .first("workDate").as("workDateMd")
//                        .count().as("docCount")
//                        .sum("reservedNumber").as("reservedNumber")
//                        .sum("availableNumber").as("availableNumber"),
//                Aggregation.sort(Sort.Direction.ASC,"workDate"),
//                Aggregation.skip((pageNum-1)*pageSize),
//                Aggregation.limit(pageSize)
        );
        AggregationResults<BookingScheduleRuleVo> aggregate = mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);

        //list集合中的每个vo对象中的各个属性没有值
        List<BookingScheduleRuleVo> list = aggregate.getMappedResults();

        //总日期个数
        return list.size();
    }
}
